
/*!
 * \file     shape.cpp
 * \brief    Shape implementation.
 * \author   Duriez Franck
 * \author   Carton Paul-Henri
 * \version  0.1
 */

#include "assert_macros.h"
#include "constant.h"
#include "shape.h"
#include "SFML/Graphics.hpp"

#include <math.h>

Shape::Shape(QSFMLWidget *displayWidget, QObject *parent, float *dt) : /*{{{*/
	    SquareContainer(displayWidget, parent, dt, Vector2i(SHAPE_SIZE,SHAPE_SIZE)),
	    m_current_orientation(Shape::Up),
	    m_initialized(false),
	    m_angle_to_travel(0.f),
	    moveLeft(&Shape::moveLeftStartingProcess, &Shape::moveLeftProcess,
	             &Shape::moveLeftStoppingProcess, this),
	    moveRight(&Shape::moveRightStartingProcess, &Shape::moveRightProcess,
	             &Shape::moveRightStoppingProcess, this),
	    moveUp(&Shape::moveUpStartingProcess, &Shape::moveUpProcess,
	             &Shape::moveUpStoppingProcess, this),
	    moveDown(&Shape::moveDownStartingProcess, &Shape::moveDownProcess,
	             &Shape::moveDownStoppingProcess, this),
	    rotateAction(&Shape::rotateStartingProcess, &Shape::rotateProcess,
	             &Shape::rotateStoppingProcess, this),
	    antiRotateAction(&Shape::antiRotateStartingProcess, &Shape::antiRotateProcess,
	             &Shape::antiRotateStoppingProcess, this),
	    m_position_in_grid(Vector2i(0,0)),
	    m_grid(NULL)
{
	setCenter(Vector2f(SQUARE_SIZE/2,SQUARE_SIZE/2));
} /*}}}*/

Shape::~Shape() /*{{{*/
{
} /*}}}*/

void Shape::format(Vector2i coord) const throw(TetrisException::ExpectedSquare) /*{{{*/
{
	if (getObject(coord))
	{
	    getObject(coord)->setPosition(Vector2f(m_position.x + m_center.x,
	                                           m_position.y + m_center.y));
	    getObject(coord)->setCenter(Vector2f(m_center.x - coord.x * SQUARE_SIZE,
	                                         m_center.y - coord.y * SQUARE_SIZE));
	}
} /*}}}*/

void Shape::setShapeType(ShapeType atype) /*{{{*/
{
	if (m_initialized) return;
	m_initialized = true;
	SHAPES; // define : int shapes[][]
	int* conf = shapes[atype];

	int tmp = 0;

	for (int i = 0; i < SHAPE_SIZE; i++) {
	    for (int j = 0; j < SHAPE_SIZE; j++) {
	        tmp = conf[j * SHAPE_SIZE + i];
	        if (tmp) {
	            setObject(Vector2i(i,j), mySquareType(conf, j*SHAPE_SIZE + i, atype));
	            format(Vector2i(i,j));
	        }
	    }
	}
} /*}}}*/

void Shape::setRandomShapeType() /*{{{*/
{
	int random = (int) sf::Randomizer::Random(0,SHAPE_TYPE_NUMBER - 1);
	setShapeType((ShapeType) random);
} /*}}}*/

void Shape::rotateInContainer(unsigned int n) /*{{{*/
{
	if (n == 0) /*{{{*/
	{
		return;
	} /*}}}*/

	for (int i = 0; i < (SHAPE_SIZE + 1) / 2; i++) { /*{{{*/
		for (int j = 0; j < (SHAPE_SIZE + 1) / 2; j++)
		{
			swap(Vector2i(SHAPE_SIZE - 1 - j, i), Vector2i(i, j));
			swap(Vector2i(SHAPE_SIZE - 1 - i, SHAPE_SIZE - 1 - j), Vector2i(SHAPE_SIZE - 1 - j, i));
			swap(Vector2i(j, SHAPE_SIZE - 1 - i), Vector2i(SHAPE_SIZE - 1 - i, SHAPE_SIZE - 1 - j));
		}
	} /*}}}*/

	if (n==1) /*{{{*/
	{
		return;
	}
	else
	{
		rotateInContainer(n-1);
	} /*}}}*/
} /*}}}*/

void Shape::antiRotateInContainer(unsigned int n) /*{{{*/
{
	if (n == 0) /*{{{*/
	{
		return;
	} /*}}}*/

	for (int i = 0; i < (SHAPE_SIZE + 1) / 2; i++) { /*{{{*/
		for (int j = 0; j < (SHAPE_SIZE + 1) / 2; j++)
		{
			swap(Vector2i(j, SHAPE_SIZE - 1 - i), Vector2i(SHAPE_SIZE - 1 - i, SHAPE_SIZE - 1 - j));
			swap(Vector2i(SHAPE_SIZE - 1 - i, SHAPE_SIZE - 1 - j), Vector2i(SHAPE_SIZE - 1 - j, i));
			swap(Vector2i(SHAPE_SIZE - 1 - j, i), Vector2i(i, j));
		}
	} /*}}}*/

	if (n==1) /*{{{*/
	{
		return;
	}
	else
	{
		rotateInContainer(n-1);
	} /*}}}*/
} /*}}}*/

/************
 * DRAWABLE *
 ************/
/*{{{*/
void Shape::draw() const /*{{{*/
{
	forAllObjects(&Square::draw);
} /*}}}*/

void Shape::update() /*{{{*/
{
	forAllObjects(&Square::update);
	moveLeft.update();
	moveRight.update();
	moveUp.update();
	moveDown.update();
	rotateAction.update();
	antiRotateAction.update();
} /*}}}*/

std::string Shape::toString() const /*{{{*/
{
	std::ostringstream res;

	for (int j = 0; j < m_size.y; j++) {
	    for (int i = 0; i < m_size.x; i++)
	    {
	        bool tmp = getObject(Vector2i(i,j));
	        res << tmp;
	    }
	    res << std::endl;
	}

	res << "orientation : " << (int) m_current_orientation << std::endl;

	return res.str();
} /*}}}*/

void Shape::setCenter(Vector2f) /*{{{*/
{
	m_center = Vector2f((((float) SHAPE_SIZE)/2) * SQUARE_SIZE,
	                    (((float) SHAPE_SIZE)/2) * SQUARE_SIZE);
} /*}}}*/

void Shape::setPosition(Vector2f position) /*{{{*/
{
	SquareContainer::setPosition(position);

	for (int i = 0; i < SHAPE_SIZE; i++) {
	    for (int j = 0; j < SHAPE_SIZE; j++)
	    {
	        if (getObject(Vector2i(i,j)))
	        getObject(Vector2i(i,j))->setPosition(Vector2f(m_position.x + m_center.x,
	                                                       m_position.y + m_center.y));
	    }
	}
} /*}}}*/

void Shape::setRotation(float angle) /*{{{*/
{
	SquareContainer::setRotation(angle);

	for (int i = 0; i < SHAPE_SIZE; i++) {
	    for (int j = 0; j < SHAPE_SIZE; j++)
	    {
	        if (getObject(Vector2i(i,j)))
	        getObject(Vector2i(i,j))->setRotation(m_angle);
	    }
	}
} /*}}}*/

void Shape::moveCenter(Vector2f) /*{{{*/
{
} /*}}}*/

void Shape::move(Vector2f dposition) /*{{{*/
{
	setPosition(m_position + dposition);
} /*}}}*/

void Shape::rotate(float dangle) /*{{{*/
{
	setRotation(m_angle + dangle);
} /*}}}*/
/*}}}*/

/********
 * GRID *
 ********/
/*{{{*/
Vector2i Shape::getPositionInGrid() const /*{{{*/
{
	return m_position_in_grid;
} /*}}}*/

void Shape::setPositionInGrid(Vector2i coord) /*{{{*/
{
	m_position_in_grid = coord;
} /*}}}*/

void Shape::setGrid(Grid *agrid) /*{{{*/
{
	m_grid = agrid;
} /*}}}*/
/*}}}*/

/*********************
 * MOVING PERMISSION *
 *********************/
/*{{{*/
bool Shape::isOnTheRightBoard() const /*{{{*/
{
	if (!m_grid) return true;

	bool isNotOnRightBoard = true;
	for (int i = 0; i < SHAPE_SIZE; i++) {
	    for(int j = 0; j < SHAPE_SIZE; j++)
	    {
	        if (getObject(Vector2i(i,j)))
	        {   // if this place is not empty
	            isNotOnRightBoard = isNotOnRightBoard && ((i + m_position_in_grid.x) < GRID_WIDTH-1);
	        }
	    }
	}
	return !isNotOnRightBoard;
} /*}}}*/

bool Shape::isOnTheLeftBoard() const /*{{{*/
{
	if (!m_grid) return true;

	bool isNotOnLeftBoard = true;
	for (int i = 0; i < SHAPE_SIZE; i++) {
	    for(int j = 0; j < SHAPE_SIZE; j++)
	    {
	        if (getObject(Vector2i(i,j)))
	        {   // if this place is not empty
	            isNotOnLeftBoard = isNotOnLeftBoard && ((i + m_position_in_grid.x) > 0);
	        }
	    }
	}
	return !isNotOnLeftBoard;
} /*}}}*/

bool Shape::isOnTheDownBoard() const /*{{{*/
{
	if (!m_grid) return true;

	bool isNotOnDownBoard = true;
	for (int i = 0; i < SHAPE_SIZE; i++) {
	    for(int j = 0; j < SHAPE_SIZE; j++)
	    {
	        if (getObject(Vector2i(i,j)))
	        {   // if this place is not empty
	            isNotOnDownBoard = isNotOnDownBoard && ((j + m_position_in_grid.y) < GRID_HEIGHT - 1);
	        }
	    }
	}
	return !isNotOnDownBoard;
} /*}}}*/

bool Shape::isOnTheUpBoard() const /*{{{*/
{
	if (!m_grid) return true;

	bool isNotOnUpBoard = true;
	for (int i = 0; i < SHAPE_SIZE; i++) {
	    for(int j = 0; j < SHAPE_SIZE; j++)
	    {
	        if (getObject(Vector2i(i,j)))
	        {   // if this place is not empty
	            isNotOnUpBoard = isNotOnUpBoard && ((j + m_position_in_grid.y) > 0);
	        }
	    }
	}
	return !isNotOnUpBoard;
} /*}}}*/

bool Shape::isASquareOnTheLeft() const /*{{{*/
{
	if (!m_grid || isOnTheLeftBoard()) return true;

	bool isNotASquareOnTheLeft = true;
	for (int i = 0; i < SHAPE_SIZE; i++) {
	    for (int j = 0; j < SHAPE_SIZE; j++)
	    {
	        if (getObject(Vector2i(i,j)))
	        {   // if this place is not empty
	            bool tmp = ! m_grid->getObject(Vector2i(m_position_in_grid.x + i - 1,
	                                                    m_position_in_grid.y + j));
	            isNotASquareOnTheLeft = isNotASquareOnTheLeft && tmp;
	        }
	    }
	}
	return !isNotASquareOnTheLeft;
} /*}}}*/

bool Shape::isASquareOnTheRight() const /*{{{*/
{
	if (!m_grid || isOnTheRightBoard()) return true;

	bool isNotASquareOnTheRight = true;
	for (int i = 0; i < SHAPE_SIZE; i++) {
	    for (int j = 0; j < SHAPE_SIZE; j++)
	    {
	        if (getObject(Vector2i(i,j)))
	        {   // if this place is not empty
	            bool tmp = ! m_grid->getObject(Vector2i(m_position_in_grid.x + i + 1,
	                                                    m_position_in_grid.y + j));
	            isNotASquareOnTheRight = isNotASquareOnTheRight && tmp;
	        }
	    }
	}
	return !isNotASquareOnTheRight;
} /*}}}*/

bool Shape::isASquareDown() const /*{{{*/
{
	if (!m_grid || isOnTheDownBoard()) return true;

	bool isNotASquareDown = true;
	for (int i = 0; i < SHAPE_SIZE; i++) {
	    for (int j = 0; j < SHAPE_SIZE; j++)
	    {
	        if (getObject(Vector2i(i,j)))
	        {   // if this place is not empty
	            bool tmp = ! m_grid->getObject(Vector2i(m_position_in_grid.x + i,
	                                                    m_position_in_grid.y + j + 1));
	            isNotASquareDown = isNotASquareDown && tmp;
	        }
	    }
	}
	return !isNotASquareDown;
} /*}}}*/

bool Shape::isASquareUp() const /*{{{*/
{
	if (!m_grid || isOnTheUpBoard()) return true;

	bool isNotASquareUp = true;
	for (int i = 0; i < SHAPE_SIZE; i++) {
	    for (int j = 0; j < SHAPE_SIZE; j++)
	    {
	        if (getObject(Vector2i(i,j)))
	        {   // if this place is not empty
	            bool tmp = ! m_grid->getObject(Vector2i(m_position_in_grid.x + i,
	                                                    m_position_in_grid.y + j - 1));
	            isNotASquareUp = isNotASquareUp && tmp;
	        }
	    }
	}
	return !isNotASquareUp;
} /*}}}*/

bool Shape::canRotate() const /*{{{*/
{
	bool cannotRotate = false;
	bool willBeASquare = false;

	for (int j = 0; j < m_size.y; j++) {
	    for (int i = 0; i < m_size.x; i++) {
	        if (m_grid->isValidCoord(Vector2i(m_position_in_grid.x + i, m_position_in_grid.y + j)))
	        {
	            willBeASquare = ((bool) getObject(Vector2i(m_size.y -1 - j, i))) &&
	                            ((bool) m_grid->getObject(Vector2i(m_position_in_grid.x + i,
	                                                               m_position_in_grid.y + j)));
	        } else {
	            willBeASquare = (bool) getObject(Vector2i(m_size.y -1 - j, i));
	        }
	        cannotRotate = cannotRotate || willBeASquare;
	    }
	}
	return !cannotRotate;
} /*}}}*/

bool Shape::canAntiRotate() const /*{{{*/
{
	bool cannotRotate = false;
	bool willBeASquare = false;

	for (int j = 0; j < m_size.y; j++) {
	    for (int i = 0; i < m_size.x; i++) {
	        if (m_grid->isValidCoord(Vector2i(m_position_in_grid.x + i, m_position_in_grid.y + j)))
	        {
	            willBeASquare = ((bool) getObject(Vector2i(j, m_size.x -1 - i))) &&
	                            ((bool) m_grid->getObject(Vector2i(m_position_in_grid.x + i,
	                                                               m_position_in_grid.y + j)));
	        } else {
	            willBeASquare = ((bool) getObject(Vector2i(j, m_size.x -1 - i)));
	        }
	        cannotRotate = cannotRotate || willBeASquare;
	    }
	}
	return !cannotRotate;
} /*}}}*/
/*}}}*/

/*************
 * DIRECTION *
 *************/
/*{{{*/
Shape::Direction& operator++(Shape::Direction& dir, int) /*{{{*/
{
//    DEBUGGER("++" << (int) dir);
	return dir = (Shape::Direction)(((int) dir + 1) % DIR_NUMBER);
} /*}}}*/

Shape::Direction& operator--(Shape::Direction& dir, int) /*{{{*/
{
//    DEBUGGER("--" << (int) dir);
	return dir = (Shape::Direction)((DIR_NUMBER + (int) dir - 1) % DIR_NUMBER);
} /*}}}*/
/*}}}*/

/****************
 *   ACTIONS    *
 ****************/
/*{{{*/
// moveLeft

void Shape::moveLeftStartingProcess() /*{{{*/
{
	if (moveRight.isProcessing()) moveRight.stop(false);
	if (!isASquareOnTheLeft()) m_position_in_grid += Vector2i(-1,0);
} /*}}}*/

void Shape::moveLeftProcess() /*{{{*/
{
	float x_final = m_position_in_grid.x * SQUARE_SIZE + m_grid->getPosition().x;
	float delta_x = fabs(x_final - getPosition().x);
	float dx = (*dt) * SHAPE_TRANSLAT_SPEED * (1 + 3.f * delta_x / SQUARE_SIZE);

	if (m_position.x - dx >= x_final)
	{
	    moveLeft.maintain();
	    move(Vector2f(-dx, 0));
	}
} /*}}}*/

void Shape::moveLeftStoppingProcess() /*{{{*/
{
	setPosition(Vector2f(m_position_in_grid.x * SQUARE_SIZE + m_grid->getPosition().x,
	            getPosition().y));
} /*}}}*/

// moveRight

void Shape::moveRightStartingProcess() /*{{{*/
{
	if (moveLeft.isProcessing()) moveRight.stop(false);
	if (!isASquareOnTheRight()) m_position_in_grid += Vector2i(1,0);
} /*}}}*/

void Shape::moveRightProcess() /*{{{*/
{
	float x_final = m_position_in_grid.x * SQUARE_SIZE + m_grid->getPosition().x;
	float delta_x = fabs(x_final - getPosition().x);
	float dx = (*dt) * SHAPE_TRANSLAT_SPEED * (1 + 3.f * delta_x / SQUARE_SIZE);

	if (m_position.x + dx <= x_final)
	{
	    moveRight.maintain();
	    move(Vector2f(dx, 0));
	}
} /*}}}*/

void Shape::moveRightStoppingProcess() /*{{{*/
{
	setPosition(Vector2f(m_position_in_grid.x * SQUARE_SIZE + m_grid->getPosition().x,
	            getPosition().y));
} /*}}}*/

// moveUp

void Shape::moveUpStartingProcess() /*{{{*/
{
	if (moveDown.isProcessing()) moveDown.stop(false);
	if (!isASquareUp()) m_position_in_grid += Vector2i(0,-1);
} /*}}}*/

void Shape::moveUpProcess() /*{{{*/
{
	float y_final = m_position_in_grid.y * SQUARE_SIZE + m_grid->getPosition().y;
	float delta_y = fabs(y_final - getPosition().y);
	float dy = (*dt) * SHAPE_TRANSLAT_SPEED * (1 + 3.f * delta_y / SQUARE_SIZE);

	if (m_position.y - dy >= y_final)
	{
	    moveUp.maintain();
	    move(Vector2f(0, -dy));
	}
} /*}}}*/

void Shape::moveUpStoppingProcess() /*{{{*/
{
	setPosition(Vector2f(getPosition().x,
	                     m_position_in_grid.y * SQUARE_SIZE + m_grid->getPosition().y));
} /*}}}*/

// moveDown

void Shape::moveDownStartingProcess() /*{{{*/
{
	if (moveUp.isProcessing()) moveUp.stop(false);
	if (!isASquareDown()) m_position_in_grid += Vector2i(0,1);
} /*}}}*/

void Shape::moveDownProcess() /*{{{*/
{
	float y_final = m_position_in_grid.y * SQUARE_SIZE + m_grid->getPosition().y;
	float delta_y = fabs(y_final - getPosition().y);
	float dy = (*dt) * SHAPE_TRANSLAT_SPEED * (1 + 3.f * delta_y / SQUARE_SIZE);

	if (m_position.y + dy <= y_final)
	{
	    moveDown.maintain();
	    move(Vector2f(0, dy));
	}
} /*}}}*/

void Shape::moveDownStoppingProcess() /*{{{*/
{
	setPosition(Vector2f(m_position_in_grid.x, m_position_in_grid.y) *
	            SQUARE_SIZE + m_grid->getPosition());
} /*}}}*/

// rotate

void Shape::rotateStartingProcess() /*{{{*/
{
	if (!canRotate())
	{
	    rotateAction.stop(false);
	    return;
	}

	if (antiRotateAction.isProcessing()) antiRotateAction.stop(false);

	rotateInContainer();

	m_angle_to_travel += 90.f;
	m_current_orientation++;
} /*}}}*/

void Shape::rotateProcess() /*{{{*/
{
	float dTheta = (*dt) * SHAPE_ROTATION_SPEED * (1 + fabs(m_angle_to_travel) / 30.f);

	//DEBUGGER("angle to travel : " << m_angle_to_travel);
	//DEBUGGER("angle           : " << m_angle);
	//DEBUGGER("angle + dTheta  : " << (m_angle + dTheta));

	if (m_angle_to_travel - dTheta >= 0)
	{
	    m_angle_to_travel -= dTheta;
	    rotateAction.maintain();
	    rotate(dTheta);
	} else {
	    m_angle_to_travel = 0.f;
	}
} /*}}}*/

void Shape::rotateStoppingProcess() /*{{{*/
{
//    DEBUGGER("stop rotation");
	if (m_angle_to_travel == 0.f) setRotation(90.f * (int) m_current_orientation);
} /*}}}*/

// antiRotate

void Shape::antiRotateStartingProcess() /*{{{*/
{
	if (!canAntiRotate())
	{
	    antiRotateAction.stop(false);
	    return;
	}
	if (rotateAction.isProcessing()) rotateAction.stop(false);

	antiRotateInContainer();

	m_current_orientation--;
	m_angle_to_travel -= 90.f;
} /*}}}*/

void Shape::antiRotateProcess() /*{{{*/
{
	float dTheta = (*dt) * SHAPE_ROTATION_SPEED * (1 + fabs(m_angle_to_travel) / 30.f);

	//DEBUGGER("angle to travel : " << m_angle_to_travel);
	//DEBUGGER("angle           : " << m_angle);
	//DEBUGGER("angle + dTheta  : " << (m_angle + dTheta));

	if (m_angle_to_travel + dTheta <= 0)
	{
	    m_angle_to_travel += dTheta;
	    antiRotateAction.maintain();
	    rotate(-dTheta);
	} else {
	    m_angle_to_travel = 0.f;
	}
} /*}}}*/

void Shape::antiRotateStoppingProcess() /*{{{*/
{
//    DEBUGGER("stop antirotation");
	if (m_angle_to_travel == 0.f) setRotation(90.f * (int) m_current_orientation);
} /*}}}*/
/*}}}*/
